/**
 * TcxReader.java
 * Version: 1.0
 * Created in 06/08/2013
 * Created by Gerson Groth
 * 
 * Copyright
 */
package activities.parser;

import java.io.File;
import java.io.IOException;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import org.apache.log4j.Logger;
import org.jdom2.Attribute;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.JDOMException;
import org.jdom2.input.SAXBuilder;

import activities.base.tcx.Activity;
import activities.base.tcx.Author;
import activities.base.tcx.BuildVersion;
import activities.base.tcx.Creator;
import activities.base.tcx.Extensions;
import activities.base.tcx.Language;
import activities.base.tcx.Lap;
import activities.base.tcx.Position;
import activities.base.tcx.SportType;
import activities.base.tcx.Track;
import activities.base.tcx.Trackpoint;
import activities.base.tcx.TrainingCenter;
import activities.base.tcx.TriggerMethod;

/**
 * The Class TcxReader.
 * 
 * @author Gerson Groth
 */
public class TcxReader {

	/** The logger. */
	private Logger logger = Logger.getLogger(TcxReader.class);

	/** The training center. */
	private TrainingCenter trainingCenter;

	/**
	 * Load tcx.
	 * 
	 * @param path
	 *            the path
	 * @return the training center
	 * @throws JDOMException
	 *             the jDOM exception
	 * @throws IOException
	 *             Signals that an I/O exception has occurred.
	 */
	public TrainingCenter loadTcx(String path) throws JDOMException,
			IOException {
		trainingCenter = new TrainingCenter();

		File tcxFile = new File(path);

		SAXBuilder builder = new SAXBuilder();

		Document document = (Document) builder.build(tcxFile);
		Element trainingCenterDatabase = document.getRootElement();

		loadActivities(trainingCenterDatabase);
		loadAuthor(trainingCenterDatabase);

		return trainingCenter;
	}

	/**
	 * Load author.
	 * 
	 * @param trainingCenterDatabase
	 *            the training center database
	 */
	private void loadAuthor(Element trainingCenterDatabase) {
		Element author = getElement(trainingCenterDatabase, "Author");

		String name = getElementAsString(author, "Name");

		Element build = getElement(author, "Build");
		Element version = getElement(build, "Version");

		BuildVersion buildVersion = createBuildVersion(version);

		Language language = new Language(getElementAsString(author, "LangID"),
				"");
		String partNumber = getElementAsString(author, "PartNumber");

		Author authorObject = new Author(name, buildVersion, language,
				partNumber);
		trainingCenter.setAuthor(authorObject);
	}

	/**
	 * Creates the build version.
	 * 
	 * @param version
	 *            the version
	 * @return the builds the version
	 */
	private BuildVersion createBuildVersion(Element version) {
		String versionMajor = getElementAsString(version, "VersionMajor");
		String versionMinor = getElementAsString(version, "VersionMinor");
		String buildMajor = getElementAsString(version, "BuildMajor");
		String buildMinor = getElementAsString(version, "BuildMinor");

		BuildVersion buildVersion = new BuildVersion(
				parseToInteger(versionMajor), parseToInteger(versionMinor),
				parseToInteger(buildMajor), parseToInteger(buildMinor));

		return buildVersion;
	}

	/**
	 * Parses the String to integer.
	 * 
	 * @param value
	 *            the value
	 * @return the int
	 */
	private int parseToInteger(String value) {
		if (value == null)
			return -1;
		int val;
		try {
			val = Integer.parseInt(value);
		} catch (Exception e) {
			val = -1;
		}

		return val;
	}

	/**
	 * Parses the String to double.
	 * 
	 * @param value
	 *            the value
	 * @return the double
	 */
	private double parseToDouble(String value) {
		if (value == null)
			return -1;
		double val;
		try {
			val = Double.parseDouble(value);
		} catch (Exception e) {
			val = -1;
		}

		return val;
	}

	/**
	 * Load activities.
	 * 
	 * @param trainingCenterDatabase
	 *            the training center database
	 */
	private void loadActivities(Element trainingCenterDatabase) {
		Element activitiesElement = getElement(trainingCenterDatabase,
				"Activities");

		List<Element> activityElement = getElements(activitiesElement,
				"Activity");

		for (Element e : activityElement) {
			Activity activity = loadActivity(e);
			trainingCenter.addActivity(activity);
		}

	}

	/**
	 * Load activity.
	 * 
	 * @param activityElement
	 *            the activity element
	 * @return the activity
	 */
	private Activity loadActivity(Element activityElement) {
		String sport = getAttributeAsString(activityElement, "Sport");
		SportType sportType = SportType.getSport(sport);

		Creator creator = loadCreator(getElement(activityElement, "Creator"));

		String id = getElementAsString(activityElement, "Id");

		Activity activity = new Activity(sportType, id, creator);

		ArrayList<Lap> laps = createLaps(activityElement);
		activity.addLaps(laps);

		return activity;
	}

	/**
	 * Creates the laps.
	 * 
	 * @param activityElement
	 *            the activity element
	 * @return the array list
	 */
	private ArrayList<Lap> createLaps(Element activityElement) {
		ArrayList<Lap> laps = new ArrayList<Lap>();
		List<Element> listLaps = getElements(activityElement, "Lap");

		for (Element lap : listLaps) {
			laps.add(createLap(lap));
		}

		return laps;
	}

	/**
	 * Parses the date.
	 * 
	 * @param date
	 *            the date
	 * @param format
	 *            the format
	 * @return the date
	 */
	private Date parseDate(String date, String format) {
		Date dateReturn = null;

		try {
			DateFormat formatter = new SimpleDateFormat(format);
			dateReturn = (java.util.Date) formatter.parse(date);
		} catch (ParseException e1) {
			logger.error("Error parsing date", e1);
		} catch (Exception e2) {
			logger.error("Error parsing date: NullPointerException", e2);
		}

		return dateReturn;
	}

	/**
	 * Creates the lap.
	 * 
	 * @param lapElement
	 *            the lap element
	 * @return the lap
	 */
	private Lap createLap(Element lapElement) {

		String date = getAttributeAsString(lapElement, "StartTime");
		Date startTime = parseDate(date, "yyyy-MM-dd'T'HH:mm:ss");

		String totalTimeSeconds = getElementAsString(lapElement,
				"TotalTimeSeconds");
		String distanceMeters = getElementAsString(lapElement, "DistanceMeters");
		String maximumSpeed = getElementAsString(lapElement, "MaximumSpeed");
		String calories = getElementAsString(lapElement, "Calories");
		String averageHeartRateBpm = getElementAsString(
				getElement(lapElement, "AverageHeartRateBpm"), "Value");
		String maximumHeartRateBpm = getElementAsString(
				getElement(lapElement, "MaximumHeartRateBpm"), "Value");
		String intensity = getElementAsString(lapElement, "Intensity");
		boolean intensityActive = false;
		if ("Active".equals(intensity))
			intensityActive = true;

		String trigger = getElementAsString(lapElement, "TriggerMethod");
		TriggerMethod triggerMethod = TriggerMethod.getMethod(trigger);

		Extensions extensions = createExtensions(getElements(getElement(
				lapElement, "Extensions")));

		Element trackElement = getElement(lapElement, "Track");
		Track track = createTrack(trackElement);

		Lap lap = new Lap(startTime, parseToDouble(totalTimeSeconds),
				parseToDouble(distanceMeters), parseToDouble(maximumSpeed),
				parseToInteger(calories), parseToInteger(averageHeartRateBpm),
				parseToInteger(maximumHeartRateBpm), intensityActive,
				triggerMethod, track, extensions);

		return lap;
	}

	/**
	 * Creates the extensions.
	 * 
	 * @param extensionsElements
	 *            the extensions elements
	 * @return the extensions
	 */
	private Extensions createExtensions(List<Element> extensionsElements) {
		Extensions extensions = new Extensions();

		for (Element e : extensionsElements) {
			List<Element> elements = getElements(e);
			for (Element ee : elements) {
				extensions.addProperty(ee.getName(), ee.getValue());
			}
		}
		return extensions;
	}

	/**
	 * Creates the track.
	 * 
	 * @param trackElement
	 *            the track element
	 * @return the track
	 */
	private Track createTrack(Element trackElement) {
		Track track = new Track();

		List<Element> trackpoints = getElements(trackElement, "Trackpoint");

		for (Element e : trackpoints) {
			track.addTrackpoint(createTrackpoint(e));
		}

		return track;
	}

	/**
	 * Creates the trackpoint.
	 * 
	 * @param trackPointElement
	 *            the track point element
	 * @return the trackpoint
	 */
	private Trackpoint createTrackpoint(Element trackPointElement) {

		String date = getElementAsString(trackPointElement, "Time");
		Date time = parseDate(date, "yyyy-MM-dd'T'HH:mm:ss");

		Position position = createPosition(getElement(trackPointElement,
				"Position"));

		String altitude = getElementAsString(trackPointElement,
				"AltitudeMeters");
		String distance = getElementAsString(trackPointElement,
				"DistanceMeters");

		String heartRateBpm = getElementAsString(
				getElement(trackPointElement, "HeartRateBpm"), "Value");

		Extensions extensions = createExtensions(getElements(getElement(
				trackPointElement, "Extensions")));

		Trackpoint trackpoint = new Trackpoint(time, position,
				parseToDouble(altitude), parseToDouble(distance),
				parseToInteger(heartRateBpm), extensions);

		return trackpoint;
	}

	/**
	 * Creates the position.
	 * 
	 * @param positionElement
	 *            the position element
	 * @return the position
	 */
	private Position createPosition(Element positionElement) {

		String latitudeString = getElementAsString(positionElement,
				"LatitudeDegrees");
		String longitudeString = getElementAsString(positionElement,
				"LongitudeDegrees");

		double latitude = parseToDouble(latitudeString);
		double longitude = parseToDouble(longitudeString);

		Position position = new Position(latitude, longitude);

		return position;
	}

	/**
	 * Load creator.
	 * 
	 * @param activityElement
	 *            the activity element
	 * @return the creator
	 */
	private Creator loadCreator(Element activityElement) {

		Element versionElement = getElement(activityElement, "Version");
		BuildVersion version = createBuildVersion(versionElement);

		String xsiType = getAttributeAsString(activityElement, "xsi:type");
		if (xsiType == null) {
			try {
				xsiType = activityElement.getAttributes().get(0).getValue();
			} catch (Exception e) {
			}
		}
		String name = getElementAsString(activityElement, "Name");
		String unitId = getElementAsString(activityElement, "UnitId");
		String productId = getElementAsString(activityElement, "ProductID");

		Creator creator = new Creator(xsiType, name, unitId, productId, version);

		return creator;
	}

	/**
	 * Gets the element.
	 * 
	 * @param element
	 *            the element
	 * @param child
	 *            the child
	 * @return the element
	 */
	private static Element getElement(Element element, String child) {
		if (element == null)
			return null;

		Element eChild = element.getChild(child, element.getNamespace());
		if (eChild == null)
			return null;

		return eChild;
	}

	/**
	 * Gets the elements.
	 * 
	 * @param element
	 *            the element
	 * @param child
	 *            the child
	 * @return the elements
	 */
	private static List<Element> getElements(Element element, String child) {
		if (element == null)
			return null;

		List<Element> eChild = element.getChildren(child,
				element.getNamespace());
		if (eChild == null)
			return null;

		return eChild;
	}

	/**
	 * Gets the elements.
	 * 
	 * @param element
	 *            the element
	 * @return the elements
	 */
	private static List<Element> getElements(Element element) {
		if (element == null)
			return null;

		List<Element> eChild = element.getChildren();
		if (eChild == null)
			return null;

		return eChild;
	}

	/**
	 * Gets the element as string.
	 * 
	 * @param element
	 *            the element
	 * @param child
	 *            the child
	 * @return the element as string
	 */
	private static String getElementAsString(Element element, String child) {
		if (element == null)
			return null;

		Element eChild = element.getChild(child, element.getNamespace());
		if (eChild == null)
			return null;

		return eChild.getText();
	}

	/**
	 * Gets the attribute.
	 * 
	 * @param element
	 *            the element
	 * @param attribute
	 *            the attribute
	 * @return the attribute
	 */
	private static Attribute getAttribute(Element element, String attribute) {
		if (element == null)
			return null;

		return element.getAttribute(attribute);
	}

	/**
	 * Gets the attribute as string.
	 * 
	 * @param element
	 *            the element
	 * @param attribute
	 *            the attribute
	 * @return the attribute as string
	 */
	private static String getAttributeAsString(Element element, String attribute) {
		if (element == null || element.getAttribute(attribute) == null)
			return null;

		return element.getAttribute(attribute).getValue();
	}
}
